﻿using JESAI.Core.Websocket.Client.Messages;
using Microsoft.Extensions.Logging;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Net.WebSockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;

namespace JESAI.Core.Websocket.Client
{
    public partial class WebsocketClient
    {
        private readonly Channel<RequestMessage> _messagesTextToSendQueue = Channel.CreateUnbounded<RequestMessage>(new UnboundedChannelOptions()
        {
            SingleReader = true,
            SingleWriter = false
        });
        private readonly Channel<ReadOnlySequence<byte>> _messagesBinaryToSendQueue = Channel.CreateUnbounded<ReadOnlySequence<byte>>(new UnboundedChannelOptions()
        {
            SingleReader = true,
            SingleWriter = false
        });

        private static readonly byte[] _emptyArray = { };

        /// <summary>
        /// 向websocket通道发送短信。
        /// 它将消息插入队列，并在另一个线程上完成实际发送
        /// </summary>
        /// <param name="message">要发送的文本消息</param>
        /// <returns>如果消息已写入队列，则返回true</returns>
        public bool Send(string message)
        {
            Validations.Validations.ValidateInput(message, nameof(message));

            return _messagesTextToSendQueue.Writer.TryWrite(new RequestTextMessage(message));
        }

        /// <summary>
        /// 向websocket通道发送二进制消息。
        /// 它将消息插入队列，并在另一个线程上完成实际发送
        /// </summary>
        /// <param name="message">要发送的二进制消息</param>
        /// <returns>如果消息已写入队列，则返回true</returns>
        public bool Send(byte[] message)
        {
            Validations.Validations.ValidateInput(message, nameof(message));

            return _messagesBinaryToSendQueue.Writer.TryWrite(new ReadOnlySequence<byte>(message));
        }

        /// <summary>
        /// 向websocket通道发送二进制段消息。
        /// 它将消息插入队列，并在另一个线程上完成实际发送
        /// </summary>
        /// <param name="message">要发送的二进制段消息</param>
        /// <returns>如果消息已写入队列，则返回true</returns>
        public bool Send(ArraySegment<byte> message)
        {
            Validations.Validations.ValidateInput(message, nameof(message));

            return _messagesBinaryToSendQueue.Writer.TryWrite(new ReadOnlySequence<byte>(message));
        }

        /// <summary>
        /// 向websocket通道发送二进制序列消息
        /// 它将消息插入队列，并在另一个线程上完成实际发送   
        /// </summary>
        /// <param name="message">要发送的二进制序列消息</param>
        /// <returns>如果消息已写入队列，则返回true</returns>
        public bool Send(ReadOnlySequence<byte> message)
        {
            Validations.Validations.ValidateInput(message, nameof(message));

            return _messagesBinaryToSendQueue.Writer.TryWrite(message);
        }

        /// <summary>
        /// 向websocket通道发送文本消息。
        /// 它不使用发送队列，
        /// 在完整的.NET Framework平台上同时发送两条消息时，请注意此问题
        /// </summary>
        /// <param name="message">要发送的文本消息</param>
        public Task SendInstant(string message)
        {
            Validations.Validations.ValidateInput(message, nameof(message));

            return SendInternalSynchronized(new RequestTextMessage(message));
        }

        /// <summary>
        /// 向websocket通道发送二进制消息。
        /// 它不使用发送队列，
        /// 在完整的.NET Framework平台上同时发送两条消息时，请注意此问题
        /// </summary>
        /// <param name="message">要发送的消息</param>
        public Task SendInstant(byte[] message)
        {
            return SendInternalSynchronized(message);
        }

        /// <summary>
        /// 将已转换的文本消息发送到websocket通道。
        /// 使用此方法可避免文本消息的双重序列化。
        /// 它将消息插入队列，并在另一个线程上完成实际发送
        /// </summary>
        /// <param name="message">要发送的消息</param>
        /// <returns>如果消息已写入队列，则返回true</returns>
        public bool SendAsText(byte[] message)
        {
            Validations.Validations.ValidateInput(message, nameof(message));

            return _messagesTextToSendQueue.Writer.TryWrite(new RequestBinaryMessage(message));
        }

        /// <summary>
        /// 将已转换的短信发送到websocket通道。
        /// 使用此方法可避免文本消息的双重序列化。
        /// 它将消息插入队列，并在另一个线程上完成实际发送
        /// </summary>
        /// <param name="message">要发送的消息</param>
        /// <returns>如果消息已写入队列，则返回true</returns>
        public bool SendAsText(ArraySegment<byte> message)
        {
            Validations.Validations.ValidateInput(message, nameof(message));

            return _messagesTextToSendQueue.Writer.TryWrite(new RequestBinarySegmentMessage(message));
        }

        /// <summary>
        /// 将已转换的短信发送到websocket通道。
        /// 使用此方法可避免文本消息的双重序列化。
        /// 它将消息插入队列，并在另一个线程上完成实际发送
        /// </summary>
        /// <param name="message">要发送的消息</param>
        /// <returns>如果消息已写入队列，则返回true</returns>
        public bool SendAsText(ReadOnlySequence<byte> message)
        {
            Validations.Validations.ValidateInput(message, nameof(message));

            return _messagesTextToSendQueue.Writer.TryWrite(new RequestBinarySequenceMessage(message));
        }

        /// <summary>
        /// 流式传输/发布假消息（通过可观察到的“MessageReceived”）。
        /// 用于测试目的，模拟服务器消息。 
        /// </summary>
        /// <param name="message">要流式传输的消息</param>
        public void StreamFakeMessage(ResponseMessage message)
        {
            Validations.Validations.ValidateInput(message, nameof(message));

            _messageReceivedSubject.OnNext(message);
        }

        /// <summary>
        /// 发送文本消息,消息来自队列
        /// </summary>
        /// <returns></returns>
        private async Task SendTextFromQueue()
        {
            try
            {
                while (await _messagesTextToSendQueue.Reader.WaitToReadAsync())
                {
                    while (_messagesTextToSendQueue.Reader.TryRead(out var message))
                    {
                        try
                        {
                            await SendInternalSynchronized(message).ConfigureAwait(false);
                        }
                        catch (Exception e)
                        {
                            _logger.LogError(e, L("发送消息失败： '{message}'. 错误: {error}"), Name, message, e.Message);
                        }
                    }
                }
            }
            catch (TaskCanceledException)
            {
                // 任务已取消，忽略
            }
            catch (OperationCanceledException)
            {
                // 操作已取消，忽略
            }
            catch (Exception e)
            {
                if (_cancellationTotal?.IsCancellationRequested == true || _disposing)
                {
                    // 处理/取消，什么都不做，退出
                    return;
                }

                _logger.LogTrace(L("发送文本线程失败，错误： {error}. 创建新的发送线程。"), Name, e.Message);
                StartBackgroundThreadForSendingText();
            }

        }
        private async Task SendPing()
        {
            while (true)
                try
                {
                    Send("ping");
                    Task.Delay(1000).Wait();
                }
                catch (Exception e)
                {
                    _logger.LogError(e, L("发送ping失败： {error}"), Name, e.Message);
                }
        }

        /// <summary>
        /// 发送二进制消息,消息来自队列
        /// </summary>
        /// <returns></returns>
        private async Task SendBinaryFromQueue()
        {
            try
            {
                while (await _messagesBinaryToSendQueue.Reader.WaitToReadAsync())
                {
                    while (_messagesBinaryToSendQueue.Reader.TryRead(out var message))
                    {
                        try
                        {
                            await SendInternalSynchronized(message).ConfigureAwait(false);
                        }
                        catch (Exception e)
                        {
                            _logger.LogError(e, L("发送二进制消息失败： '{message}'. 错误: {error}"), Name, message, e.Message);
                        }
                    }
                }
            }
            catch (TaskCanceledException)
            {
                // 任务已取消，忽略
            }
            catch (OperationCanceledException)
            {
                // 操作已取消，忽略
            }
            catch (Exception e)
            {
                if (_cancellationTotal?.IsCancellationRequested == true || _disposing)
                {
                    // 处理/取消，什么都不做，退出
                    return;
                }

                _logger.LogTrace(L("发送二进制线程失败，错误： {error}. 创建新的发送线程。"), Name, e.Message);
                StartBackgroundThreadForSendingBinary();
            }

        }

        /// <summary>
        /// 开启一个发送文本队列的后台线程
        /// </summary>
        private void StartBackgroundThreadForSendingText()
        {
            _ = Task.Factory.StartNew(_ => SendTextFromQueue(), TaskCreationOptions.LongRunning, _cancellationTotal?.Token ?? CancellationToken.None);
        }

        /// <summary>
        /// 开启一个发送二进制队列的后台线程
        /// </summary>
        private void StartBackgroundThreadForSendingBinary()
        {
            _ = Task.Factory.StartNew(_ => SendBinaryFromQueue(), TaskCreationOptions.LongRunning, _cancellationTotal?.Token ?? CancellationToken.None);
        }

        private void StartBackgroundThreadForHeartBeat()
        {
            _ = Task.Factory.StartNew(_ => SendPing(), TaskCreationOptions.LongRunning, _cancellationTotal?.Token ?? CancellationToken.None);
        }

        /// <summary>
        /// 内部发送同步消息
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        private async Task SendInternalSynchronized(RequestMessage message)
        {
            using (await _locker.LockAsync())
            {
                await SendInternal(message);
            }
        }

        /// <summary>
        /// 发送消息的内部方法
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentException"></exception>
        private async Task SendInternal(RequestMessage message)
        {
            switch (message)
            {
                case RequestTextMessage textMessage:
                    await SendTextMessage(textMessage).ConfigureAwait(false);
                    break;
                case RequestBinaryMessage binaryMessage:
                    await SendBinaryMessage(binaryMessage).ConfigureAwait(false);
                    break;
                case RequestBinarySegmentMessage segmentMessage:
                    await SendBinarySegmentMessage(segmentMessage).ConfigureAwait(false);
                    break;
                case RequestBinarySequenceMessage sequenceMessage:
                    await SendBinarySequenceMessage(sequenceMessage).ConfigureAwait(false);
                    break;
                default:
                    throw new ArgumentException($"未知的消息类型: {message.GetType()}");
            }
        }

        /// <summary>
        /// 发送文本消息
        /// </summary>
        /// <param name="textMessage"></param>
        /// <returns></returns>
        private async Task SendTextMessage(RequestTextMessage textMessage)
        {
            var payload = MemoryMarshal.AsMemory<byte>(GetEncoding().GetBytes(textMessage.Text));
            await SendInternal(payload, WebSocketMessageType.Text).ConfigureAwait(false);
        }

        /// <summary>
        /// 发送二进制消息
        /// </summary>
        /// <param name="binaryMessage"></param>
        /// <returns></returns>
        private async Task SendBinaryMessage(RequestBinaryMessage binaryMessage)
        {
            var payload = MemoryMarshal.AsMemory<byte>(binaryMessage.Data);
            await SendInternal(payload, WebSocketMessageType.Text).ConfigureAwait(false);
        }

        /// <summary>
        /// 发送二进制段消息
        /// </summary>
        /// <param name="segmentMessage"></param>
        /// <returns></returns>
        private async Task SendBinarySegmentMessage(RequestBinarySegmentMessage segmentMessage)
        {
            await SendInternal(segmentMessage.Data, WebSocketMessageType.Text).ConfigureAwait(false);
        }

        /// <summary>
        /// 发送二进制序列消息
        /// </summary>
        /// <param name="sequenceMessage"></param>
        /// <returns></returns>
        private async Task SendBinarySequenceMessage(RequestBinarySequenceMessage sequenceMessage)
        {
            await SendInternal(sequenceMessage.Data, WebSocketMessageType.Text).ConfigureAwait(false);
        }

        /// <summary>
        /// 同步发送消息
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        private async Task SendInternalSynchronized(ReadOnlySequence<byte> message)
        {
            using (await _locker.LockAsync())
            {
                await SendInternal(message, WebSocketMessageType.Binary);
            }
        }

        /// <summary>
        /// 同步发送消息
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        private async Task SendInternalSynchronized(ReadOnlyMemory<byte> message)
        {
            using (await _locker.LockAsync())
            {
                await SendInternal(message, WebSocketMessageType.Binary);
            }
        }

        /// <summary>
        /// 发送的内部方法
        /// </summary>
        /// <param name="payload"></param>
        /// <param name="messageType"></param>
        /// <returns></returns>
        private async Task SendInternal(ReadOnlySequence<byte> payload, WebSocketMessageType messageType)
        {
            if (payload.IsSingleSegment)
            {
                await SendInternal(payload.First, messageType).ConfigureAwait(false);
                return;
            }

            if (!CheckClientConnection(payload.Length))
            {
                return;
            }

            foreach (var memory in payload)
            {
                await _client!
                    .SendAsync(memory, messageType, false, _cancellation?.Token ?? CancellationToken.None)
                    .ConfigureAwait(false);
            }

            await _client!
                .SendAsync(_emptyArray, messageType, true, _cancellation?.Token ?? CancellationToken.None)
                .ConfigureAwait(false);
        }

        /// <summary>
        /// 发送消息的内部方法
        /// </summary>
        /// <param name="payload"></param>
        /// <param name="messageType"></param>
        /// <returns></returns>
        private async Task SendInternal(ReadOnlyMemory<byte> payload, WebSocketMessageType messageType)
        {
            if (!CheckClientConnection(payload.Length))
            {
                return;
            }

            await _client!
                .SendAsync(payload, messageType, true, _cancellation?.Token ?? CancellationToken.None)
                .ConfigureAwait(false);
        }

        /// <summary>
        /// 检查客户端是否连接
        /// </summary>
        /// <param name="length"></param>
        /// <returns></returns>
        private bool CheckClientConnection(long length)
        {
            if (!IsClientConnected())
            {
                _logger.LogDebug(L("客户端未连接到服务器，无法发送二进制文件，长度：{length}"), Name, length);
                return false;
            }

            _logger.LogTrace(L("发送二进制文件，长度： {length}"), Name, length);
            return true;
        }
    }
}
